
#include <iostream>
#include <fstream>
#include <deque>
#include <string.h>
#include <stdlib.h>

#ifdef _WIN32
#include <time.h>
#endif

/** reads the specified file and returns a pointer to the memory */
char* readFile(char *fileName);
/** returns ture if this is a legal input, otherwise false.. this is
    needed to check the input file for # which would mess up the
    reassembly process */
bool checkFile(char *text);
/** writes the fragments to the file. returns true if everything went well
    text points to the text to write*/
bool writeFile(char *fileName, char* text);

class RandomFragmentBox {

public:
	RandomFragmentBox(const char *const text, const unsigned int fragmentLength, const unsigned int textLength);
	
	/** writes a random fragment to the specified stream
	    then it deletes the fragment so that it will not
	    be written again by another call of writeFragment*/
	void writeFragment(std::ofstream* out);
	bool empty();
	unsigned int size();
	
private:
	
	struct Fragment {
		Fragment(int pos, int len) : pos(pos), len(len) {}
		int pos;
		int len;
	};
	
	const char *const text;
	const unsigned int fragmentLength;
	const unsigned int textLength;
	std::deque<Fragment> fragments;
};

//////////////////////////////////////////
//                 main
//////////////////////////////////////////
int main(int args, char* argv[])
{
	
	//check input
	if(args < 2) {
		
		std::cout << "no file specified (add file name to call)" << std::endl;
		return 1;
	}
	
	//read input file
	char* inputFileName= argv[1];
	char* text= readFile(inputFileName);
	
	if(text == NULL) {
		
		std::cout << "cant read file (check if it exists)" << std::endl;
		return 1;
	}
	
	if(!checkFile(text)) {
		
		std::cout << "there is a '#' in the input file, this is not allowed" << std::endl;
		return 1;
	}
	
	//write output file
	char* outputFileName= new char[strlen(inputFileName)+strlen("_fragmented")+1];
	strcpy(outputFileName, inputFileName);
	strcat(outputFileName, "_fragmented");
	
	
	if(!writeFile(outputFileName, text)) {
		
		std::cout << "cant write file (check if file already exists)" << std::endl;
		return 1;
	}
	
	//clean up resources
	delete [] text;
	delete [] outputFileName;
	
	return 0;
}

//////////////////////////////////////////////
//                 readFile
//////////////////////////////////////////////
char* readFile(char* fileName)
{
	
	std::ifstream in(fileName, std::ios::binary);
	
	if(!in.good()) {
		return NULL;
	}
	
	in.seekg(0, std::ios::end);
	unsigned int fileLength= static_cast<unsigned int>(in.tellg());
	in.seekg(0, std::ios::beg);
	
	char* text= new char[fileLength+1]; //string sould be null terminated
	
	in.read(text, fileLength);
	text[fileLength]= '\0';
	
	return text;
}

//////////////////////////////////////////////
//                 checkFile
//////////////////////////////////////////////
bool checkFile(char* text)
{
	
	while(*text != '\0') {
		
		if(*text == '#') return false;
		text++;
	}
	
	return true;
}

///////////////////////////////////////////////
//                 writeFile
///////////////////////////////////////////////
bool writeFile(char* fileName, char* text)
{
	
	//create output stream
	std::ofstream out(fileName);
	if(!out.good()) {
		return false;
	}
	srand(static_cast<unsigned int>(time(NULL)));
	
	//create the random boxes which take care of splitting the
	//text string into smaller substrings and returing them in
	//random order by calling writeFragment
	std::deque<RandomFragmentBox*> boxes;
	int numFragments= 0;

	for(int i=0; i<5; i++) {
		boxes.push_back(new RandomFragmentBox(text, 500, strlen(text)));
		numFragments+= boxes.back()->size();
	}
	
	out << numFragments;
	//writing the content of all random boxes into the outstream
	while(!boxes.empty()) {
		
		int random= rand()%boxes.size();
		
		if(boxes[random]->empty()) {
			
			delete boxes[random];
			boxes.erase(boxes.begin()+random);
			continue;
		}
		
		out << "#";
		boxes[random]->writeFragment(&out);
		out << "#";
	}
	
	return true;
}

///////////////////////////////////////////////////////
//                 RandomFragmentBox
///////////////////////////////////////////////////////
RandomFragmentBox::RandomFragmentBox(const char *const text, const unsigned int fragmentLength, const unsigned int textLength)
: text(text)
, fragmentLength(fragmentLength)
, textLength(textLength)
{
	
	//this splits the input text string into smaller pieces
	//this is done by saving the start position and the length
	//of the smaller pieces in the queu
	for(unsigned int i=0; i<textLength;) {
		
		unsigned int len= fragmentLength + ((rand()%fragmentLength/2) - fragmentLength/4);
		
		if(i+len > textLength) {
			
			fragments.push_back(Fragment(i, textLength-i));
		} else {
			
			fragments.push_back(Fragment(i, len));
		}
		
		i+= len;
	}
}

///////////////////////////////////////////////////
//                 writeFragment
///////////////////////////////////////////////////
void RandomFragmentBox::writeFragment(std::ofstream* out)
{
	
	//gets a random fragment out of the queu and writes it
	//to the specified output stream
	int random= rand() % fragments.size();
	const char* source= &text[fragments[random].pos];
	int len= fragments[random].len;
	
	out->write(source, len);
	
	fragments.erase(fragments.begin()+random);
	
	return;
}

///////////////////////////////////////////
//                 empty
///////////////////////////////////////////
bool RandomFragmentBox::empty()
{
	
	return fragments.empty();
}


//////////////////////////////////////////
//                 size
//////////////////////////////////////////
unsigned int RandomFragmentBox::size()
{
	
	return fragments.size();
}